tree grammar BestpaperEval;  
options {
	tokenVocab=Bestpaper; // read token types from Expr.tokens file
	ASTLabelType=CommonTree; // what is Java type of nodes?
}

@header {
package com.googlecode.bestpaper;

import org.apache.commons.lang.StringUtils;
import com.googlecode.bestpaper.output.pdf.book.PDFBook;
import com.googlecode.bestpaper.output.pdf.book.PDFChapter;
import com.googlecode.bestpaper.output.pdf.book.PDFParagraph;
import com.googlecode.bestpaper.output.pdf.book.PDFSection;
import com.googlecode.bestpaper.output.pdf.table.PDFCell;
import com.googlecode.bestpaper.output.pdf.table.PDFTable;
import com.googlecode.bestpaper.output.pdf.link.PDFLink;
import com.googlecode.bestpaper.output.pdf.typeface.PDFBold;
import com.googlecode.bestpaper.output.pdf.typeface.PDFItalic;
import com.googlecode.bestpaper.output.pdf.typeface.PDFStrike;
import com.googlecode.bestpaper.output.pdf.typeface.PDFString;

}

@members {
private final static String BOLD = "*";
private final static String ITALIC = "_";
private final static String STRIKE = "~~";
private final static String LINK_START = "[";
private final static String LINK_END = "]";

private PDFTable table = null;
private int boldPointer = -1;
private int strikePointer = -1;
private int italicPointer = -1;
private int linkPointer = -1;

private void appendInvalidTypeface(PDFParagraph para, String nowLine){
	String invalidTypeface = null;
	if (italicPointer >= 0) {
		invalidTypeface = ITALIC + nowLine.substring(this.italicPointer);
		this.italicPointer = -1;
	} else if(boldPointer >=0) {
		invalidTypeface = BOLD + nowLine.substring(this.boldPointer);
		this.boldPointer = -1;
	} else if(strikePointer >=0) {
		invalidTypeface = STRIKE + nowLine.substring(this.strikePointer);
		this.strikePointer = -1;
	} else if(linkPointer >=0) {
		invalidTypeface = LINK_START + nowLine.substring(this.linkPointer);
	}
	if (invalidTypeface != null) {
		para.addChild(new PDFString(invalidTypeface));
	}
}
	
private void appendWord(PDFParagraph para, String nowLine, String word){
	int nowIndex = nowLine.length();
	// --- start typeface
	if(word.startsWith(BOLD)){
		appendInvalidTypeface(para, nowLine);
		this.boldPointer = nowIndex + BOLD.length();
	}			
	if(word.startsWith(ITALIC)){
		appendInvalidTypeface(para, nowLine);
		this.italicPointer = nowIndex + ITALIC.length();
	}			 
	if(word.startsWith(STRIKE)){
		appendInvalidTypeface(para, nowLine);
		this.strikePointer = nowIndex + STRIKE.length();
	}
	if(word.startsWith(LINK_START)){
		appendInvalidTypeface(para, nowLine);
		this.linkPointer = nowIndex + LINK_START.length();
	}
	// add common string			
	if (this.boldPointer < 0 && this.italicPointer < 0 && this.strikePointer < 0 && this.linkPointer < 0){
		para.addChild(new PDFString(word));			
	}
	//--- end typeface
	if (word.equals(BOLD) || word.equals(ITALIC) || word.equals(STRIKE) || word.equals(LINK_START)) {

	} else {
		// move line index
		nowLine = nowLine += word;
		nowIndex = nowLine.length();	
		if(word.endsWith(BOLD) && this.boldPointer >= 0) {
			String bold = nowLine.substring(this.boldPointer, nowIndex - BOLD.length());
			para.addChild(new PDFBold(bold));
			this.boldPointer = -1;
		}
		if(word.endsWith(ITALIC) && this.italicPointer >= 0) {
			String italic = nowLine.substring(this.italicPointer, nowIndex - ITALIC.length());
			para.addChild(new PDFItalic(italic));
			this.italicPointer = -1;
		}			 			
		if(word.endsWith(STRIKE) && this.strikePointer >= 0) {
			String strike = nowLine.substring(this.strikePointer, nowIndex - STRIKE.length());
			para.addChild(new PDFStrike(strike));
			this.strikePointer = -1;
		}
		if(word.endsWith(LINK_END) && this.linkPointer >=0) {
			String link = nowLine.substring(this.linkPointer, nowIndex - LINK_END.length());
			para.addChild(new PDFLink(link));
			this.linkPointer = -1;
		}
	}	
}

private List<PDFSection> sections = new ArrayList<PDFSection>();

private void appendSection(PDFSection section){
	sections.add(section);
}

private PDFSection searchFather(int childLayer){
	PDFSection father = null;
	if(this.sections.size()==0) return father;
	for(int i = sections.size() - 1; i >=0; i--) {
		PDFSection section = sections.get(i);
		if (section.getLayer() < childLayer){
			father = section;
			break;
		}
	}
	return father;
}


}


book returns[PDFBook book]
	@init{
		$book = new PDFBook();
	}
	:	^(BOOK chapter[$book]+)
	;
chapter[PDFBook book]   
	@init{
		PDFChapter chapter = new PDFChapter();
	}
	@after{
		$book.addChild(chapter);
	}
	:	
	^(CHAPTER chapter_heading[chapter] content[null, chapter]? section[chapter]+)
	| ^(CHAPTER chapter_heading[chapter] content[null, chapter]?)
	;     
chapter_heading[PDFChapter chapter]
	:	^(CHAPTER_HEADING title)
	{
		$chapter.setTitle($title.value);
	}
	;
	section[PDFChapter chapter]  
	@init{
		PDFSection section = new PDFSection();	
	}
	@after{
		int layer = section.getLayer();
		PDFSection father = this.searchFather(layer);
		if (father == null) {
			$chapter.addChild(section);			
		} else {
			father.addChild(section);
		}
		this.appendSection(section);
	}
	:  
	^(SECTION heading[section] content[section, null]) 
	|	^(SECTION heading[section])
	;    
heading[PDFSection section]
    :	
    heading1 
    {
    	$section.setTitle($heading1.value);
    	$section.setLayer(1);
    }	
    | heading2
    {
    	$section.setTitle($heading2.value);
    	$section.setLayer(2);
    }    
    | heading3 
    {
    	$section.setTitle($heading3.value);
    	$section.setLayer(3);    	
    }  
    | heading4 
    {
    	$section.setTitle($heading4.value);
    	$section.setLayer(4);    	
    }  
    | heading5 
    {
    	$section.setTitle($heading5.value);
    	$section.setLayer(5);    	
    }  
    | heading6 
    {
    	$section.setTitle($heading6.value);
    	$section.setLayer(6);    	
    }                  
    ;      
heading1  returns[String value]
    :   ^(LEVEL1 title)
    {
    	$value = $title.value;
    }
    ;  
heading2  returns[String value]
    :   ^(LEVEL2 title)
    {
    	$value = $title.value;
    }
    ;  
heading3  returns[String value]
    :   ^(LEVEL3 title)
    {
    	$value = $title.value;
    }
    ;  
heading4  returns[String value]
    :   ^(LEVEL4 title)
    {
    	$value = $title.value;
    }    
    ;  
heading5  returns[String value]
    :   ^(LEVEL5 title)
    {
    	$value = $title.value;
    }    
    ;  
heading6  returns[String value]
    :   ^(LEVEL6 title)
    {
    	$value = $title.value;
    }
    ;      
title returns[String value]     
    :   line
    {
    	$value = $line.b.toString();
    }
    ;
content[PDFSection section, PDFChapter chapter]
    :   ^(PARAGRAPH add_seg[section, chapter]+)
    {
		if(this.table != null){
			if(section != null) {
				$section.addChild(this.table);
			} else if(chapter != null) {
				$chapter.addChild(this.table);
			}
			//trim temp table
			this.table = null;
		}	
    }
    ;
add_seg[PDFSection section, PDFChapter chapter]
	:	line
	{
		if(this.table != null){
			if(section != null) {
				$section.addChild(this.table);
			} else if(chapter != null) {
				$chapter.addChild(this.table);
			}
			//trim temp table
			this.table = null;
		}	
		PDFParagraph para = $line.para;
		if(section != null) {
			$section.addChild(para);
		} else if(chapter != null) {
			$chapter.addChild(para);
		}
	}
	|
	{
		List<PDFParagraph> cells = new ArrayList<PDFParagraph>();
	}	
	^(ROW row[cells]+)
	{
		//new table if not exist
		if(this.table == null){
			this.table = new PDFTable(cells.size());
		}	
		for(PDFParagraph cell : cells){
			this.table.addChild(cell);
		}	
	}
	;
row[List cells]
	: line
	{
		$cells.add($line.para);
	}
	;
line  returns[StringBuilder b, PDFParagraph para]
	@init{
		$b = new StringBuilder();
		$para = new PDFParagraph();
	}
	@after{
		String nowLine = $b.toString();
		//--- end typeface
		this.appendInvalidTypeface(retval.para, nowLine);
	}
    :	(word[$b, $para] space[$b, $para])* word[$b, $para]
    ;
word[StringBuilder b, PDFParagraph para]
	: WORD
	{
		String word = $WORD.text;
		appendWord(para, $b.toString(), word);
		$b.append(word);
	}
	;
space[StringBuilder b, PDFParagraph para]
	: SPACE
	{
		String word = $SPACE.text;
		appendWord(para, $b.toString(), word);
		$b.append(word);
	}
	;